﻿using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ServerSidePhysics.Core
{
    public class PhysicsClient
    {
        public TcpClient tcpClient;
        public Thread socketProcessor;
        public bool handshaken = false;

        public struct OPCodes
        {
            public static byte[] handshakeOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x1 };
            public static byte[] handshakeResponseOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x2 };
            public static byte[] requestWorldInitOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x3 };
            public static byte[] requestWorldInitResponseOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x4 };
            public static byte[] worldUpdateOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x5 };
        }

        public PhysicsClient(TcpClient client)
        {
            tcpClient = client;

            socketProcessor = new Thread(new ThreadStart(SocketProcessor));
            socketProcessor.Start();
        }

        public void SocketProcessor()
        {
            NetworkStream ns = tcpClient.GetStream();

            byte[] buffer = new byte[1024];
            
            while(true)
            {
                try
                {
                    int size = ns.Read(buffer, 0, 1024);

                    byte[] tempBuffer = buffer;
                    ProcessBuffer(tempBuffer, size);
                }
                catch(IOException ex)
                {
                    if ((ex.InnerException as Win32Exception).ErrorCode == 10054)
                    {
                        StaticHelpers.Log("Client disconnected");
                        lock(Physics.clients)
                        {
                            Physics.clients.Remove(this);
                            StaticHelpers.Log("Client removed, connected clients: " + Physics.clients.Count.ToString());
                        }
                        break;
                    }
                }
            }

            return;
        }

        public void SendByteResponse(byte[] msg, bool opCodeOnly = true, bool isWorldUpdateOrWorldInit = false, bool isUpdate = false)
        {
            byte[] header = new byte[1];
            if (opCodeOnly)
            {
                header = new byte[2] { 0x1, 0x2 };
            }
            else if (opCodeOnly == false && isWorldUpdateOrWorldInit == true && isUpdate == false)
            {
                header = new byte[2] { 0x1, 0x3 };
            }
            else if(opCodeOnly == false && isWorldUpdateOrWorldInit == true && isUpdate == true)
            {
                header = new byte[2] { 0x1, 0x4 };
            }
            else
            {
                header = new byte[1];
            }

            try
            {
                byte[] buffer = new byte[header.Length + msg.Length];
                header.CopyTo(buffer, 0);
                msg.CopyTo(buffer, header.Length);

                tcpClient.GetStream().Write(buffer, 0, buffer.Length);
            }
            catch
            {
                lock(Physics.clients)
                {
                    Physics.clients.Remove(this);
                    StaticHelpers.Log("Client removed, connected clients: " + Physics.clients.Count.ToString());
                }
            }
        }

        public byte[] BuildResponse(byte[] msg, byte[] header)
        {
            List<byte> res = new List<byte>();
            res.AddRange(header);
            res.AddRange(msg);

            return res.ToArray<byte>();
        }

        public void SendWorldInit()
        {
            Dictionary<int, Vector2> list = new Dictionary<int, Vector2>();

            foreach(Body b in Physics.pWorld.BodyList)
            {
                list.Add(b.BodyId, b.Position);
            }

            byte[] res = BuildResponse(StaticHelpers.GetBodyArray(list), OPCodes.requestWorldInitResponseOpcode);

            SendByteResponse(res, false, true);
        }

        public void ProcessBuffer(byte[] buffer, int size)
        {

            switch(buffer[0])
            {
                case 0x1:
                    if(buffer[1] == 0x1)
                    {
                        StaticHelpers.Log(Encoding.UTF8.GetString(buffer, 2, size));
                    }
                    else if(buffer[1] == 0x2)
                    {
                        Array.Resize<byte>(ref buffer, size);
                        lock (buffer)
                        {
                            byte[] msg = buffer.Skip(2).ToArray();

                            if (msg.SequenceEqual(OPCodes.handshakeOpcode))
                            {
                                StaticHelpers.Log("Handshake successfull!");
                                SendByteResponse(OPCodes.handshakeResponseOpcode);
                                handshaken = true;
                            }
                            else if (msg.SequenceEqual(OPCodes.requestWorldInitOpcode))
                            {
                                SendWorldInit();
                            }
                        }

                    }
                    break;
            }

        }
    }
}
